package com.ruoyi.oss.service.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringPool;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.config.properties.OssProperties;
import com.ruoyi.oss.domain.SysOss;
import com.ruoyi.oss.domain.bo.UploadResult;
import com.ruoyi.oss.ienum.OssType;
import com.ruoyi.oss.service.CloudOssService;
import io.minio.*;
import io.minio.errors.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Slf4j
public class MinioCloudOssServiceImpl implements CloudOssService {

    private final OssProperties ossProperties;

    public MinioCloudOssServiceImpl(OssProperties ossProperties) {
        this.ossProperties = ossProperties;
    }

    /**
     * 构建客户端 需要手动关闭
     *
     * @return 客户端
     */
    private MinioClient buildMinioClient() {
        OssProperties.MinioProperties minioProperties = ossProperties.getMinio();
        try {
            String endpoint = minioProperties.getEndpoint();
            String accessKey = minioProperties.getAccessKey();
            String secretKey = minioProperties.getSecretKey();
            Assert.isFalse(StringUtils.isAnyBlank(endpoint, accessKey, secretKey), () -> new ServiceException("配置Minio服务错误，请检查配置参数"));
            return MinioClient.builder()
                    .endpoint(endpoint)
                    .credentials(accessKey, secretKey)
                    .build();
        } catch (Exception e) {
            log.error("", e);
            throw new ServiceException("配置Minio服务错误，请检查配置参数");
        }
    }

    /**
     * 上传文件至OSS
     *
     * @param multipartFile 多部份文件
     * @param fileSuffix    文件后缀
     * @return 上传文件的结果
     */
    @Override
    public UploadResult upload(MultipartFile multipartFile, String fileSuffix) throws IOException {
        String originalFilename = multipartFile.getOriginalFilename();
        String contentType = multipartFile.getContentType();
        OssProperties.MinioProperties minioProperties = ossProperties.getMinio();
        String endpoint = minioProperties.getEndpoint();
        String bucketName = minioProperties.getBucketName();
        Assert.notBlank(bucketName, () -> new ServiceException("配置Minio服务错误，请检查配置参数"));
        String prefix = minioProperties.getPrefix();
        String path = getPath(prefix, fileSuffix);
        MinioClient minioClient = buildMinioClient();
        try (InputStream inputStream = multipartFile.getInputStream()) {
            boolean bucketExists = minioClient.bucketExists(BucketExistsArgs
                    .builder()
                    .bucket(bucketName)
                    .build());
            if (!bucketExists) {
                //不存在就创建桶
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
            }
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(path)
                    .contentType(StrUtil.blankToDefault(contentType, MediaType.APPLICATION_OCTET_STREAM_VALUE))
                    .stream(inputStream, inputStream.available(), -1)
                    .build());
            if (log.isDebugEnabled()) {
                log.debug("'{}' is successfully uploaded as object '{}' to bucket '{}'. ", path, originalFilename, bucketName);
                log.debug("上传Minio OSS文件结果:[etag-{},versionId-{}],", objectWriteResponse.etag(), objectWriteResponse.versionId());
            }
            UploadResult uploadResult = new UploadResult(OssType.MINIO);
            String url = endpoint + StringPool.SLASH + bucketName + StringPool.SLASH + path;
            uploadResult.setUrl(url);
            uploadResult.setOssPath(path);
            String fileName = path.substring(path.lastIndexOf('/') + 1);
            uploadResult.setFileName(fileName);
            uploadResult.setBucketName(bucketName);
            return uploadResult;
        } catch (ServerException | InsufficientDataException | ErrorResponseException | NoSuchAlgorithmException |
                 InvalidKeyException | InvalidResponseException | XmlParserException | InternalException e) {
            log.error("Minio OSS上传异常:", e);
            throw new ServiceException("OSS文件上传出现异常,请联系管理员!");
        } finally {
            try {
                minioClient.close();
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }


    /**
     * 下载文件
     *
     * @param sysOss              文件上传记录
     * @param httpServletRequest  请求
     * @param httpServletResponse 响应
     * @return 响应体
     */
    @Override
    public ResponseEntity<StreamingResponseBody> download(SysOss sysOss, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        try {
            String bucketName = sysOss.getBucketName();
            String originalName = sysOss.getOriginalName();
            String ossPath = sysOss.getOssPath();
            MinioClient minioClient = buildMinioClient();
            // 获取文件的总大小
            long fileSize = minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(ossPath)
                    .build()).size();
            String rangeHeader = ServletUtil.getHeader(httpServletRequest, HttpHeaders.RANGE, StandardCharsets.UTF_8);
            long startByte;
            long endByte;
            if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
                String[] ranges = rangeHeader.substring(6).split("-");
                startByte = Long.parseLong(ranges[0].trim());
                endByte = (ranges.length > 1 && !ranges[1].isEmpty()) ? Long.parseLong(ranges[1].trim()) : fileSize - 1;
            } else {
                startByte = 0;
                endByte = fileSize - 1;
            }
            // 确保范围有效
            endByte = Math.min(endByte, fileSize - 1);
            long length = endByte - startByte + 1;
            GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(ossPath)
                    .offset(startByte)
                    .length(length)
                    .build());
            HttpHeaders httpHeaders = new HttpHeaders();
            // 根据文件扩展名获取MediaType
            Optional<MediaType> optionalMediaType = MediaTypeFactory.getMediaType(originalName);
            MediaType mediaType = optionalMediaType.orElse(MediaType.APPLICATION_OCTET_STREAM);
            httpHeaders.setContentType(mediaType);
            // 创建正确编码的文件名
            ContentDisposition contentDisposition = ContentDisposition
                    .builder("attachment")
                    .filename(originalName, StandardCharsets.UTF_8)
                    .build();
            httpHeaders.setContentDisposition(contentDisposition);
            //内容长度
            httpHeaders.setContentLength(length);
            HttpStatus status = HttpStatus.OK;
            if (StringUtils.isNotNull(rangeHeader)) {
                //断点续传的位置
                List<HttpRange> ranges = new ArrayList<>();
                ranges.add(HttpRange.createByteRange(startByte, endByte));
                httpHeaders.setRange(ranges);
                status = HttpStatus.PARTIAL_CONTENT;
            }
            return ResponseEntity.status(status)
                    .headers(httpHeaders)
                    .body(outputStream -> {
                        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(getObjectResponse)) {
                            IoUtil.copy(bufferedInputStream, outputStream);
                        } finally {
                            try {
                                minioClient.close();
                            } catch (Exception e) {
                                log.error("", e);
                            }
                        }
                    });
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
                 XmlParserException e) {
            log.error("", e);
            httpServletResponse.setCharacterEncoding("utf-8");
            return ResponseEntity.internalServerError()
                    .contentType(MediaType.TEXT_PLAIN)
                    .body(o -> {
                        String errorMessage = "下载文件出现异常!";
                        byte[] bytes = errorMessage.getBytes(StandardCharsets.UTF_8);
                        o.write(bytes);
                    });
        }
    }
}
